﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.NetworkInformation;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using Castle.Core;
using StateTheater.Web;
using StateTheaterDataModel;
using System.Linq;
using Castle.MonoRail.Framework;
using System.Xml.Serialization;


namespace StateTheater
{
	public static class Util
	{
		//		static string application = "NJ";

		private static Regex _seoSpacesRegex;
		private const string SEOSPACESPATTERN = @"(\s|\-|&nbsp;|\%20|\+)+";
		private static Regex _seoHtmlEntsRegex;
		private const string SEOHTMLENTSPATTERN = @"&(\w+|#\d+);";
		private static Regex _seoNonWordRegex;
		private const string SEONONWORDPATTERN = @"[^\w\-]|^\-+|\-+$";

		private const string SEOClearPattern = @"&\w+;|&\#\d+;|<(/?)[\w ]+/?>|[^\w\-]|^\-+|\-+$|^The-|^A-|^An-";
		private static Regex _seoClearPattern;

		//private static Regex fixDatesRegex;
		//private const string fixDatesPattern = @"""\\/Date\((\d+)([-+]\d\d)(\d\d)\)\\/""";
		//private const string fixDatesReplacement = @"new Date($1 + 3600000*$2 + 60000*$3)";

		static Util()
		{
			_seoSpacesRegex = new System.Text.RegularExpressions.Regex(
				SEOSPACESPATTERN,
				RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline
				);
			_seoHtmlEntsRegex = new System.Text.RegularExpressions.Regex(
				SEOHTMLENTSPATTERN,
				RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline
				);
			_seoNonWordRegex = new System.Text.RegularExpressions.Regex(
				SEONONWORDPATTERN,
				RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline
				);

			_seoClearPattern = new System.Text.RegularExpressions.Regex(
				SEOClearPattern,
				RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline
				);
			//			fixDatesRegex =  new Regex(fixDatesPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Singleline );

		}

		public static string ToPWHashString(string Pwd)
		{
			if (String.IsNullOrEmpty(Pwd))
			{
				throw new ArgumentException("Password must be given", "Pwd");
			}
			SHA256 sha = new SHA256Managed();

			byte[] ba = sha.ComputeHash(Encoding.ASCII.GetBytes(Pwd));
			StringBuilder sb = new StringBuilder(ba.Length * 2);
			foreach (byte b in ba)
			{
				sb.AppendFormat("{0:x2}", b);
			}

			return sb.ToString();
		}


		public static string AdjTitle(string title)
		{
			if (String.IsNullOrEmpty(title) == null)
				return "";

			int pos = title.IndexOf('~');
			if (pos > -1)
			{
				return title.Substring(pos + 1) + ' ' + title.Substring(0, pos);
			}
			return title;
		}

		public static string FixPhone(string rawphone)
		{
			Regex pat = new Regex(@"^1?-?\(?(\d{3})\)?[\s-.]*(\d{3})[\s-.]*(\d{2})\s*(\d{2})\s*(x\d*)?\s*$");
			return pat.Replace(rawphone, "+1 $1 $2-$3$4$5");
		}

		//public static string FixJsonDates(string json)
		//{
		//    return fixDatesRegex.Replace(json, fixDatesReplacement);
		//}


		//public static string ConnectionString
		//{
		//    get
		//    {
		//        string connStrName = ConfigurationManager.AppSettings[application];
		//        return ConfigurationManager.ConnectionStrings[connStrName].ConnectionString;
		//    }
		//}

		public static StateTheaterDataContext DataContext
		{
			get
			{
				return (HttpContext.Current.ApplicationInstance as StateTheaterApplication).Container.Resolve<StateTheaterDataContext>();
			}

		}

		public static string With(this string format, params object[] args)
		{
			return String.Format(format, args);
		}


		/// <summary>
		/// Resizes the image to fit within the given maximum sizes, while retain aspect ratio.
		/// </summary>
		/// <remarks>
		/// This will only reduce a too large image.  It does not expand an image.
		/// </remarks>
		/// <param name="image">The image.</param>
		/// <param name="max_width">The maximum width of the desired image.</param>
		/// <param name="max_height">The maximum height of the desired image.</param>
		/// <returns>A new image object</returns>
		public static Bitmap ResizeImage(Image image, int max_width, int max_height)
		{
			Color seethru = Color.White; //  Color.Transparent;	// Color.FromArgb(0, 0, 0, 0);
			Bitmap bitmap = ResizeImage(image, max_width, max_height, seethru);
			//			bitmap.MakeTransparent(seethru);
			return bitmap;

		}

		public static Bitmap ResizeImage(Image image, int max_width, int max_height, Color background)
		{
			int height = image.Height;
			int width = image.Width;

			if (max_height == 0) max_height = Int32.MaxValue;
			if (max_width == 0) max_width = Int32.MaxValue;

			if (height > max_height || width > max_width)
			{
#if (false)
				const long scaleResolution = 1000;
				long scaleW = (max_width * scaleResolution) / width;
				long  scaleH = (max_height * scaleResolution) / height;
				long  scale = Math.Min(scaleW, scaleH);

				height = (int) ((height * scale) / scaleResolution);
				width = (int)((width * scale) / scaleResolution);
#else
				double scaleW = max_width / (double)width;
				double scaleH = max_height / (double)height;
				double scale = Math.Min(scaleW, scaleH);

				height = (int)(height * scale);
				width = (int)(width * scale);
#endif
			}

			int offsetX = (max_width - width) / 2;
			int offsetY = (max_height - height) / 2;

			// Create a new image object we want to resize and draw the existing image into this one

			Bitmap bitmap = new Bitmap(max_width, max_height, PixelFormat.Format32bppArgb);
			Graphics gfx = Graphics.FromImage(bitmap);
			gfx.FillRectangle(new SolidBrush(background), 0, 0, max_width, max_height);

			//			gfx.InterpolationMode = InterpolationMode.Bicubic;
			gfx.InterpolationMode = InterpolationMode.HighQualityBilinear;
			gfx.PixelOffsetMode = PixelOffsetMode.Half;

			// Now resize the existing image object into the new image object
			gfx.DrawImage(image, offsetX, offsetY, width, height);
			return (bitmap);
		}

		static Dictionary<string, int> fontMap = new Dictionary<string, int>() { { "x-small", 8 }, { "small", 12 }, { "medium", 14 }, { "large", 16 }, { "x-large", 20 } };
		public static void TextOnImage(string text, Stream output, ImageFormat imageFormat, string fontsize)
		{
			//            Format = PixelFormat.Format24bppRgb;
			int emsize = 14;		// default to medium is badly specified.
			fontMap.TryGetValue(fontsize, out emsize);

			Font stringFont = new Font("Arial", emsize, FontStyle.Bold);
			SolidBrush stringBrush = new SolidBrush(Color.Blue);

			SizeF size;
			using (Bitmap NewImage = new Bitmap(100, 16, PixelFormat.Format24bppRgb))
			{
				using (Graphics Canvas = Graphics.FromImage(NewImage))
				{
					size = Canvas.MeasureString(text, stringFont);
				}
			}
			using (Bitmap NewImage = new Bitmap((int)size.Width, (int)size.Height + 0, PixelFormat.Format24bppRgb))
			{
				var trans = Color.White;  // Color.Transparent; //  Color.FromArgb(0, 1, 1, 1);
				using (Graphics Canvas = Graphics.FromImage(NewImage))
				{
					Canvas.FillRectangle(new SolidBrush(trans), 0, 0, (int)size.Width, (int)size.Height + 6);
					Canvas.Clear(trans);
					Canvas.DrawString(text, stringFont, stringBrush, 0f, 0f);
					if (trans != Color.White)
						NewImage.MakeTransparent();
					NewImage.Save(output, imageFormat);
				}
			}
		}

#if false
		private Bitmap ResizeImage(Stream streamImage, int maxWidth, int maxHeight)
		{
			Bitmap originalImage = new Bitmap(streamImage);
			int newWidth = originalImage.Width;
			int newHeight = originalImage.Height;
			double aspectRatio = (double)originalImage.Width / (double)originalImage.Height;

			if (aspectRatio <= 1 && originalImage.Width > maxWidth)
			{
				newWidth = maxWidth;
				newHeight = (int)Math.Round(newWidth / aspectRatio);
			}
			else if (aspectRatio > 1 && originalImage.Height > maxHeight)
			{
				newHeight = maxHeight;
				newWidth = (int)Math.Round(newHeight * aspectRatio);
			}

			Bitmap newImage = new Bitmap(originalImage, newWidth, newHeight);

			Graphics g = Graphics.FromImage(newImage);
			g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
			g.DrawImage(originalImage, 0, 0, newImage.Width, newImage.Height);

			originalImage.Dispose();

			return newImage;
		}
#endif

		internal static NameValueCollection DecodeBlock(string code)
		{
			StringBuilder sb = new StringBuilder(code.Length / 2);
			for (int i = 0; i < code.Length; i += 2)
			{
				string str = code.Substring(i, 2);
				byte b = (byte)(byte.Parse(str, NumberStyles.AllowHexSpecifier) ^ 0xA9);
				sb.Append((char)b);
			}
			string decoded = sb.ToString();
			return HttpUtility.ParseQueryString(decoded);
		}

		public static string Encode(string email)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(email)).Replace('=', '-').Replace('/', '~');
		}

		public static string Decode(string code)
		{
			return Encoding.UTF8.GetString(Convert.FromBase64String(code.Replace('-', '=').Replace('~', '/')));
		}


		static public string BuildList(this IList<string> items)
		{
			return BuildList(items, ", ", " & ");
		}

		static public string BuildList<T>(this IList<T> items, string separator)
		{
			return BuildList(items, separator, separator, t => t.ToString());
		}

		static public string BuildList<T>(this IList<T> items, string separator, string lastSeparator)
		{
			return BuildList(items, separator, lastSeparator, t => t.ToString());
		}
		static public string BuildList<T>(this IList<T> items, string separator, Func<T, string> tostring)
		{
			return BuildList(items, separator, separator, tostring);

		}
		static public string BuildList<T>(this IList<T> items, string separator, string lastSeparator, Func<T, string> tostring)
		{
			int last = items.Count;
			StringBuilder str = new StringBuilder();
			switch (last)
			{
				case 0:
					return "";

				case 1:
					return tostring(items[0]);

				case 2:
					str.Append(tostring(items[0]));
					break;

				default:
					for (int i = 0; i < last - 2; ++i)
					{
						str.Append(tostring(items[i]));
						str.Append(separator);
					}
					str.Append(tostring(items[last - 2]));
					break;
			}
			str.Append(lastSeparator);
			str.Append(tostring(items[last - 1]));
			return str.ToString();
		}


		/// <summary>
		/// Parses the string to escape certain characters
		/// </summary>
		/// <param name="value">The value.</param>
		/// <param name="withSpaces">if set to <c>true</c> space are changed to dashes. Otherwise, they are removed.</param>
		/// <returns></returns>
		public static string SEOEscape(string value, bool withSpaces)
		{
			value = _seoSpacesRegex.Replace(value, withSpaces ? "-" : "");
			value = _seoClearPattern.Replace(value, "");
			//            value = _seoHtmlEntsRegex.Replace(value, "");
			//            value = _seoNonWordRegex.Replace(value, "");
			return (value);
		}

		//internal static object SEOEscape(string p)
		//{
		//    throw new NotImplementedException();
		//}

		public static IEnumerator<XElement> StreamElement(string pathname, string tagName)
		{
			using (var xr = XmlReader.Create(pathname))
			{
				xr.MoveToContent();
				while (xr.ReadToFollowing(tagName))
				{
					yield return XElement.Load(xr);
				}
				xr.Close();
			}
		}

		public static void OutputXml(this Controller ctrl, object obj)
		{
			var xs = new XmlSerializer(obj.GetType());
			xs.Serialize(ctrl.Context.Response.OutputStream, obj);
			ctrl.CancelView();

		}



		public static string CombineUrl(this string baseUrl, string relativeUrl)
		{
			UriBuilder baseUri = new UriBuilder(baseUrl);
		
			Uri newUri;
			if (Uri.TryCreate(baseUri.Uri, relativeUrl, out newUri))
				return newUri.ToString();
			throw new ArgumentException("Unable to combine uri values");

		}

		public static List<TraceRtLine> TraceRoute(string dest)
		{
			if (String.IsNullOrEmpty(dest))
				return new List<TraceRtLine>();

			IPAddress ipAddress = IPAddress.Parse(dest);
			var lines = new List<TraceRtLine>();
			using (Ping pingSender = new Ping())
			{
				var pingOptions = new PingOptions();
				pingOptions.DontFragment = true;
				var stopWatch = new Stopwatch();
				var bytes = new byte[32];

				const int maxHops = 20;

				for (int hop = 1; hop <= maxHops; hop++)
				{
					pingOptions.Ttl = hop;
					stopWatch.Reset();
					stopWatch.Start();
					PingReply pingReply = pingSender.Send(
						ipAddress,
						1500,
						bytes, pingOptions);

					stopWatch.Stop();
					var trl = new TraceRtLine();
					trl.Hop = hop;
					if (pingReply.Status == IPStatus.TimedOut)
					{
						trl.IPAddress = "Timed Out";
						trl.Host = "";
					}
					else
					{
						trl.IPAddress = pingReply.Address.ToString();
						ThreadPool.QueueUserWorkItem(state =>
													 {
														 var trl2 = state as TraceRtLine;
														 try
														 {
															 var ipHost = Dns.GetHostEntry(trl2.IPAddress);
															 trl2.Host = ipHost.HostName;
														 }
														 catch (Exception ex)
														 {
															 trl2.Host = ex.Message;
														 }
													 }, trl);
					}
					trl.Time = stopWatch.ElapsedMilliseconds;
					lines.Add(trl);
					if (pingReply.Status == IPStatus.Success || ipAddress.Equals(pingReply.Address))
						break;
				}
			}
			return lines;
		}

		static public U RangeLookup<T, U>(this IEnumerable<Pair<T, U>> table, T value) where T : IComparable<T>
		{
			var lower = table.First();
			foreach (var upper in table)
			{
				if (lower.First.CompareTo(value) <= 0 && value.CompareTo(upper.First) < 0)
					return lower.Second;
				lower = upper;
			}
			return lower.Second;
		}

	}
		public class TraceRtLine
		{
			public int Hop { get; set; }
			public string IPAddress {get; set;}
			public string Host  {get; set;}
			public long Time { get; set; }
		}

	public static class Pair
	{
		static public Pair<T, U> New<T, U>(T first, U second)
		{
			return new Pair<T, U>(first, second);
		}
	}
}

